04-RDS연결업그레이드
RDS 연결 웹앱 업그레이드
이전 단계에서 만든 "Hello World" 서버를 데이터베이스 연결된 실제 웹 애플리케이션으로 업그레이드합니다.
전제조건
- AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/02-NodeJS설치배포 완료 (PM2로 webapp이 실행 중)
- AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/03-RDS생성 완료 (RDS 인스턴스 생성됨)
0. 패키지 설치
# webapp 디렉토리로 이동
cd ~/webapp/nodejs-app
# npm 초기화가 안 되어 있으면 package.json 생성
npm init -y
# mysql2 설치
npm install mysql2
# express 설치 (아직 설치 안 했다면)
npm install express
# PM2에서 앱 실행 전 확인
ls node_modules | grep mysql2

1. 기존 앱 백업
기존 Hello World 서버를 백업해둡니다.
# 홈 디렉토리로 이동
cd ~
# 기존 webapp 폴더 백업
cp -r webapp/nodejs-app webapp/nodejs-app-hello-backup
# 작업할 폴더로 이동
cd webapp/nodejs-app
2. 데이터베이스 연결 앱 코드로 교체
app.js 파일 교체
기존의 간단한 웹서버를 데이터베이스 연결이 가능한 버전으로 교체합니다.
# 기존 PM2 프로세스 중지
pm2 stop webapp
# app.js 파일을 데이터베이스 연결 버전으로 교체
cat > app.js << 'EOF'
// AWS 웹앱 메인 서버 파일
// 이 파일은 웹사이트의 두뇌 역할을 합니다
// 필요한 라이브러리들을 불러옵니다
const express = require('express'); // 웹서버 만드는 도구
const mysql = require('mysql2'); // MySQL 데이터베이스 연결 도구
const path = require('path'); // 파일 경로 처리 도구
// Express 웹서버를 만듭니다
const app = express();
const port = 3000; // 웹서버가 사용할 포트 번호
// 미들웨어 설정 (요청 처리를 위한 준비 작업)
app.use(express.json()); // JSON 데이터를 이해할 수 있게 설정
app.use(express.static('public')); // HTML, CSS 파일들을 서비스할 폴더 설정
console.log('웹서버를 시작하는 중입니다...');
// 환경 변수에서 데이터베이스 설정 가져오기
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
};
// 필수 환경 변수 검증
if (!dbConfig.host || !dbConfig.user || !dbConfig.password || !dbConfig.database) {
console.error('필수 환경 변수가 누락되었습니다:');
console.error('다음 환경 변수들을 모두 설정해주세요:');
console.error('- DB_HOST: RDS 엔드포인트');
console.error('- DB_USER: 데이터베이스 사용자명');
console.error('- DB_PASSWORD: 데이터베이스 비밀번호');
console.error('- DB_NAME: 데이터베이스 이름');
console.error('');
console.error('설정 예시:');
console.error('export DB_HOST="your-rds-endpoint.amazonaws.com"');
console.error('export DB_USER="admin"');
console.error('export DB_PASSWORD="your-password"');
console.error('export DB_NAME="webapp_db"');
process.exit(1);
}
console.log('데이터베이스에 연결하는 중입니다...');
console.log('연결 정보:', {
host: dbConfig.host,
user: dbConfig.user,
database: dbConfig.database
// 보안상 비밀번호는 출력하지 않습니다
});
// MySQL 데이터베이스에 연결합니다
const db = mysql.createConnection(dbConfig);
// 데이터베이스 연결 및 테이블 생성
db.connect((err) => {
if (err) {
console.error('데이터베이스 연결 실패:', err.message);
console.log('해결방법:');
console.log('1. RDS 엔드포인트가 정확한지 확인하세요');
console.log('2. RDS 보안 그룹에서 3306 포트가 열려있는지 확인하세요');
console.log('3. 사용자명과 비밀번호가 정확한지 확인하세요');
console.log('4. 환경 변수가 올바르게 설정되어 있는지 확인하세요');
return;
}
console.log('데이터베이스 연결 성공!');
// users 테이블을 자동으로 생성합니다 (없다면)
const createTableQuery = `
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
db.query(createTableQuery, (err) => {
if (err) {
console.error('테이블 생성 실패:', err.message);
} else {
console.log('users 테이블이 준비되었습니다!');
}
});
});
// 메인 페이지 라우트 - 사용자가 웹사이트에 처음 접속할 때
app.get('/', (req, res) => {
console.log('누군가 메인 페이지에 접속했습니다');
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 사용자 목록 조회 API - 등록된 사용자들을 보여줍니다
app.get('/users', (req, res) => {
console.log('사용자 목록을 요청받았습니다');
// 데이터베이스에서 모든 사용자를 최신순으로 조회
const query = 'SELECT * FROM users ORDER BY created_at DESC';
db.query(query, (err, results) => {
if (err) {
console.error('사용자 조회 실패:', err.message);
return res.json([]); // 빈 배열을 반환 (에러가 나도 웹사이트는 동작)
}
console.log(`${results.length}명의 사용자를 찾았습니다`);
res.json(results); // 사용자 목록을 JSON 형태로 반환
});
});
// 새 사용자 등록 API - 사용자가 등록 버튼을 눌렀을 때
app.post('/users', (req, res) => {
const { name, email } = req.body; // 전송받은 이름과 이메일을 추출
console.log('새 사용자 등록 요청:', { name, email });
// 입력값 검증 - 이름과 이메일이 비어있지 않은지 확인
if (!name || !email) {
console.log('이름 또는 이메일이 비어있습니다');
return res.status(400).json({
error: 'Name and email required',
message: '이름과 이메일을 모두 입력해주세요'
});
}
// 이름과 이메일 공백 제거
const cleanName = name.trim();
const cleanEmail = email.trim();
if (!cleanName || !cleanEmail) {
console.log('이름 또는 이메일이 공백만 포함되어 있습니다');
return res.status(400).json({
error: 'Name and email cannot be empty',
message: '이름과 이메일에 내용을 입력해주세요'
});
}
// 데이터베이스에 새 사용자 추가
const query = 'INSERT INTO users (name, email) VALUES (?, ?)';
db.query(query, [cleanName, cleanEmail], (err, result) => {
if (err) {
console.error('사용자 등록 실패:', err.message);
return res.status(500).json({
error: 'Database error',
message: '데이터베이스 저장 중 오류가 발생했습니다'
});
}
console.log(`새 사용자 등록 성공! ID: ${result.insertId}`);
res.json({
success: true,
id: result.insertId,
message: '사용자가 성공적으로 등록되었습니다',
user: { id: result.insertId, name: cleanName, email: cleanEmail }
});
});
});
// 웹서버를 시작합니다!
app.listen(port, '0.0.0.0', () => {
console.log('');
console.log('웹서버가 성공적으로 시작되었습니다!');
console.log(`서버 주소: http://localhost:${port}`);
console.log(`외부 접속: http://여러분의EC2퍼블릭IP:${port}`);
console.log('');
console.log('서버를 중지하려면 Ctrl+C를 누르세요');
console.log('');
});
// 프로그램이 종료될 때 데이터베이스 연결을 정리합니다
process.on('SIGINT', () => {
console.log('서버를 종료하는 중입니다...');
db.end(() => {
console.log('데이터베이스 연결이 종료되었습니다');
process.exit(0);
});
});
EOF
3. 프론트엔드 파일 생성
사용자가 데이터를 입력하고 조회할 수 있는 웹 페이지를 만듭니다.
public 폴더 생성 및 HTML 파일 작성
# public 폴더 생성
mkdir -p public
# index.html 파일 생성
cat > public/index.html << 'EOF'
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AWS 웹앱 실습</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>AWS 클라우드 웹 애플리케이션</h1>
<p>EC2 + RDS 연동 실습</p>
</header>
<main>
<!-- 사용자 등록 섹션 -->
<section class="form-section">
<h2>새 사용자 등록</h2>
<form id="userForm">
<div class="form-group">
<label for="name">이름:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">이메일:</label>
<input type="email" id="email" name="email" required>
</div>
<button type="submit">등록하기</button>
</form>
<div id="message"></div>
</section>
<!-- 사용자 목록 섹션 -->
<section class="list-section">
<h2>등록된 사용자 목록</h2>
<button id="refreshBtn">목록 새로고침</button>
<div id="userList">
<p>사용자 목록을 불러오는 중...</p>
</div>
</section>
</main>
<footer>
<p>AWS EDU Week2 실습 - Node.js + MySQL</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>
EOF
CSS 스타일 파일 생성
# style.css 파일 생성
cat > public/style.css << 'EOF'
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
overflow: hidden;
}
header {
background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
color: white;
padding: 2rem;
text-align: center;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.1rem;
opacity: 0.9;
}
main {
padding: 2rem;
}
.form-section, .list-section {
margin-bottom: 3rem;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #4ECDC4;
}
.form-section h2, .list-section h2 {
color: #333;
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
}
input[type="text"], input[type="email"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s;
}
input[type="text"]:focus, input[type="email"]:focus {
outline: none;
border-color: #4ECDC4;
box-shadow: 0 0 0 2px rgba(78, 205, 196, 0.2);
}
button {
background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
color: white;
padding: 0.75rem 2rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: transform 0.2s, box-shadow 0.2s;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
button:active {
transform: translateY(0);
}
#refreshBtn {
background: linear-gradient(45deg, #4ECDC4, #667eea);
margin-bottom: 1rem;
}
#message {
margin-top: 1rem;
padding: 0.75rem;
border-radius: 4px;
display: none;
}
#message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
#message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.user-item {
background: white;
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 4px;
border-left: 3px solid #4ECDC4;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.user-item h3 {
color: #333;
margin-bottom: 0.5rem;
}
.user-item p {
color: #666;
margin-bottom: 0.25rem;
}
.user-item small {
color: #999;
}
footer {
background: #333;
color: white;
text-align: center;
padding: 1rem;
}
.loading {
text-align: center;
color: #666;
font-style: italic;
}
@media (max-width: 600px) {
.container {
margin: 0;
border-radius: 0;
}
header {
padding: 1.5rem;
}
header h1 {
font-size: 2rem;
}
main {
padding: 1rem;
}
}
EOF
JavaScript 파일 생성
# script.js 파일 생성
cat > public/script.js << 'EOF'
// 페이지가 로드되면 실행되는 함수
document.addEventListener('DOMContentLoaded', function() {
const userForm = document.getElementById('userForm');
const messageDiv = document.getElementById('message');
const userListDiv = document.getElementById('userList');
const refreshBtn = document.getElementById('refreshBtn');
// 초기 사용자 목록 로드
loadUsers();
// 사용자 등록 폼 제출 처리
userForm.addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('name').value.trim();
const email = document.getElementById('email').value.trim();
if (!name || !email) {
showMessage('이름과 이메일을 모두 입력해주세요.', 'error');
return;
}
// 서버에 사용자 등록 요청
fetch('/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: name, email: email })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showMessage(data.message, 'success');
userForm.reset(); // 폼 초기화
loadUsers(); // 사용자 목록 새로고침
} else {
showMessage(data.message || '등록 중 오류가 발생했습니다.', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showMessage('서버와의 통신 중 오류가 발생했습니다.', 'error');
});
});
// 새로고침 버튼 클릭 처리
refreshBtn.addEventListener('click', loadUsers);
// 사용자 목록을 서버에서 가져와 화면에 표시
function loadUsers() {
userListDiv.innerHTML = '<p class="loading">사용자 목록을 불러오는 중...</p>';
fetch('/users')
.then(response => response.json())
.then(users => {
if (users.length === 0) {
userListDiv.innerHTML = '<p>등록된 사용자가 없습니다.</p>';
return;
}
const userHTML = users.map(user => `
<div class="user-item">
<h3>${escapeHtml(user.name)}</h3>
<p>이메일: ${escapeHtml(user.email)}</p>
<small>등록일: ${new Date(user.created_at).toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}</small>
</div>
`).join('');
userListDiv.innerHTML = userHTML;
})
.catch(error => {
console.error('Error:', error);
userListDiv.innerHTML = '<p>사용자 목록을 불러오는데 실패했습니다.</p>';
});
}
// 메시지 표시 함수
function showMessage(message, type) {
messageDiv.textContent = message;
messageDiv.className = type;
messageDiv.style.display = 'block';
// 3초 후 메시지 자동 숨김
setTimeout(() => {
messageDiv.style.display = 'none';
}, 3000);
}
// XSS 방지를 위한 HTML 이스케이프 함수
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
});
EOF
4. 환경 변수 설정
데이터베이스 연결 정보를 환경 변수로 설정합니다.
# RDS 인스턴스 정보를 환경 변수로 설정
# 여러분의 실제 RDS 엔드포인트와 비밀번호를 입력하세요!
export DB_HOST="your-rds-endpoint.cx6mc24eklg5.ap-northeast-3.rds.amazonaws.com"
export DB_USER="admin"
export DB_PASSWORD="your-password"
export DB_NAME="webapp_db"
# 설정 확인
echo "DB_HOST: $DB_HOST"
echo "DB_USER: $DB_USER"
echo "DB_NAME: $DB_NAME"
중요: 실제 RDS 엔드포인트와 설정한 비밀번호로 교체해야 합니다!
5. 애플리케이션 재시작
업그레이드된 앱을 실행합니다.
# 기존 프로세스 완전히 중지 및 삭제
pm2 delete webapp
# 새로운 앱으로 시작
pm2 start app.js --name webapp
# 상태 확인
pm2 status
# 로그 확인 (연결 상태 모니터링)
pm2 logs webapp
6. 웹 애플리케이션 테스트
브라우저 접속 테스트
-
웹 브라우저를 열고 접속:
http://[EC2-Public-IP]:3000 -
데이터베이스 연결 확인:
- PM2 로그에서 "데이터베이스 연결 성공!" 메시지 확인
- "users 테이블이 준비되었습니다!" 메시지 확인
기능 테스트
-
사용자 등록 테스트:
- 이름: 홍길동
- 이메일: hong@test.com
- "등록하기" 버튼 클릭
- 성공 메시지 확인
-
사용자 목록 조회:
- 등록한 사용자가 목록에 표시되는지 확인
- "목록 새로고침" 버튼 동작 확인
7. 연결 상태 확인
터미널에서 직접 데이터베이스 확인
# MySQL 클라이언트로 직접 연결하여 데이터 확인
mysql -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME
# MySQL에 접속되면 다음 명령어 실행:
# SELECT * FROM users;
# quit
로그 모니터링
# 실시간 로그 확인
pm2 logs webapp --lines 50
# 사용자 등록/조회 시 로그 메시지 확인:
# - "새 사용자 등록 요청"
# - "새 사용자 등록 성공"
# - "사용자 목록을 요청받았습니다"
완료 체크리스트
**이제 EC2에서 실행되는 Node.js 웹 애플리케이션이 RDS MySQL 데이터베이스와 성공적으로 연결되었습니다.
관련 문서: AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/Week2-전체가이드, AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/02-NodeJS설치배포, AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/03-RDS생성